package com.zzyl.nursing.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 *
 * @author ruoyi
 * @date 2025-10-28
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Value("${alertRule.roleRepair}")
    private String roleRepair; //维修员角色

    @Value("${alertRule.roleAdmin}")
    private String roleAdmin; //超级管理员

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private IAlertDataService alertDataService;

    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 过滤报警规则，将符合要求的数据存入报警数据表
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void alertRuleFilter() {
        //1. 查询所有报警规则
        Long count = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (count == 0) {
            log.error("[报警规则校验]没有查询到任何报警规则，无需处理");
            return;
        }

        //2. 查询所有设备上报的数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_DATA_LATEST);
        if (CollUtil.isEmpty(values)) {
            log.error("[报警规则校验]没有查询到设备上报的数据，无需处理");
            return;
        }

        //3. 设备上报的数据格式化处理（转换类型为List）
        List<DeviceData> deviceDataListAll = getDeviceData(values);

        //4. 遍历设备上报的数据
        for (DeviceData deviceData : deviceDataListAll) {
            // 前置校验：如果设备上报数据的时间是很久之间的时间，那就忽略。
            int timeout = 60 * 60;//单位是秒，如果超过1个小时的报警数据就不再处理
            LocalDateTime alertTime = deviceData.getAlarmTime();
            LocalDateTime now = LocalDateTime.now();
            long between = Math.abs(LocalDateTimeUtil.between(alertTime, now, ChronoUnit.SECONDS));
            if (between > timeout) {
                log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 数据上报时间太久，已超过{}秒，不再处理", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), timeout);
                continue;
            }

            //4.1 查询设备关联的报警规则数据
            Set<AlertRule> alertRuleListAll = getAlertRules(deviceData);
            if (alertRuleListAll == null) continue;

            //4.2 遍历报警规则数据
            for (AlertRule alertRule : alertRuleListAll) {
                //4.2.1 第一层校验：判断是否在规则的生效时间内
                String period = alertRule.getAlertEffectivePeriod();//时间段字符串，格式如：00:00:00~23:59:59
                List<String> periodList = Arrays.asList(period.split("~"));
                LocalTime startTime = LocalTime.parse(periodList.get(0));
                LocalTime endTime = LocalTime.parse(periodList.get(1));
                LocalTime deviceAlertTime = deviceData.getAlarmTime().toLocalTime(); //设备上报数据的时间（到时分秒，不要年月日）
                if ((deviceAlertTime.isBefore(startTime)) || (deviceAlertTime.isAfter(endTime))) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{},设备上报数据时间不在报警规则生效时间段内", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName());
                    continue;
                }

                //4.2.2 第二层校验：判断数据是否达到规则的阈值
                String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_KEY,deviceData.getIotId(), deviceData.getFunctionId(),alertRule.getId());

                String operator = alertRule.getOperator();//比较运算符的符号
                double alertValue = (double) alertRule.getValue();//阈值
                double alarmValue = Double.valueOf(deviceData.getDataValue()).doubleValue();//设备上报的数据
                if ((operator.equals("<") && alarmValue >= alertValue) || (operator.equals(">=") && alarmValue < alertValue)) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 现在值：{}, 未到到告警规则阈值：{}", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), alarmValue, alertValue);

                    //删除持续周期报警次数缓存数据
                    redisTemplate.delete(countKey);
                    continue;
                }

                //4.2.3 第三层校验：判断沉默周期
                String silentKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_KEY,deviceData.getIotId(), deviceData.getFunctionId(),alertRule.getId());
                String silentValue = redisTemplate.opsForValue().get(silentKey);
                if(StrUtil.isNotBlank(silentValue)) {
                    Long ttl = redisTemplate.getExpire(silentKey);
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在沉默周期内，无需处理，沉默周期还剩余{}秒 ", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), ttl );
                    continue;
                }

                //4.2.4 第四层校验：判断持续周期
                Integer duration = alertRule.getDuration();//持续周期上限值

                Long durationCount = redisTemplate.opsForValue().increment(countKey);
                if(durationCount<duration) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在持续周期内（未达上限），现在次数：{}，上限次数：{} ", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), durationCount, duration);
                    continue;
                }

                //报警沉默周期缓存数据
                Integer alertSilentPeriod = alertRule.getAlertSilentPeriod();//沉默周期的时间值（单位是分钟）
                redisTemplate.opsForValue().set(silentKey,"aa", alertSilentPeriod, TimeUnit.MINUTES);

                //删除持续周期报警次数缓存数据
                redisTemplate.delete(countKey);

                //4.2.5 报警规则校验通过，则处理数据完毕后新增报警数据

                //4.2.5.1 查询护理员或维修员ID列表
                List<Long> nursingIdList = null;
                //如果是老人数据
                if(alertRule.getAlertDataType()==0) {
                    //如果是移动设备（随身设备），通过老人关联查询护理人员ID列表
                    if(deviceData.getLocationType()==0) {
                        nursingIdList = deviceMapper.selectNursingIdsByMoveIotId(deviceData.getIotId());
                    } else {
                        //如果是固定设备，通过床位关联查询护理人员ID列表
                        nursingIdList = deviceMapper.selectNursingIdsByFixedIotId(deviceData.getIotId());
                    }
                } else {
                    //如果是设备数据，则只查询维修员ID列表
                    nursingIdList = sysRoleMapper.findUserIdsByRoleName(roleRepair);
                }

                //4.2.5.2 查询管理员ID列表
                List<Long> adminUserIdList = sysRoleMapper.findUserIdsByRoleName(roleAdmin);

                //4.2.5.3 合并用户ID列表
                Set<Long> userIdList = CollUtil.unionDistinct(nursingIdList, adminUserIdList);
                if(CollUtil.isEmpty(userIdList)) {
                    log.error("[报警规则校验]没有查询到任何用户ID，设备ID：{}", deviceData.getIotId());
                    continue;
                }

                //4.2.5.3 构建报警数据

                // 构建基础报警数据对象
                AlertData baseData = BeanUtil.toBean(deviceData, AlertData.class);
                baseData.setId(null);//这个要注意设置为null，否则会出错
                baseData.setAlertRuleId(alertRule.getId()); //报警规则ID
                baseData.setStatus(0); //待处理状态
                baseData.setType(alertRule.getAlertDataType());
                //报警原因，格式：功能名称+运算符+阈值+持续周期+沉默周期
                String alertReasonTemplate = "功能：{}{}{}，持续周期：{}，沉默周期：{}";
                String alertReason = CharSequenceUtil.format(alertReasonTemplate, alertRule.getFunctionName(), alertRule.getOperator(),
                        alertRule.getValue(), alertRule.getDuration(), alertRule.getAlertSilentPeriod());
                baseData.setAlertReason(alertReason);

                // 遍历构建每个用户（谁来处理，就写对应用户ID）的报警数据
                List<AlertData> alertDataList = userIdList.stream().map(x -> {
                    AlertData alertData = BeanUtil.toBean(baseData, AlertData.class);
                    alertData.setUserId(x);
                    return alertData;
                }).collect(Collectors.toList());

                //4.2.5.4 批量新增报警数据
                alertDataService.saveBatch(alertDataList);
                log.info("[报警规则校验]新增报警数据完毕。设备ID：{}，报警信息：{}", deviceData.getIotId(), alertReason);

                //4.2.5.5 发送WebSocket消息通知给指定的客户端用户
                userIdList.stream().forEach(x->{

                    AlertNotifyVo notifyVo = BeanUtil.toBean(deviceData, AlertNotifyVo.class);
                    notifyVo.setFunctionName(alertRule.getFunctionName()); //功能名
                    notifyVo.setNotifyType(1);//报警类型，1：报警
                    webSocketServer.publishMessage(x.toString(), JSONUtil.toJsonStr(notifyVo));
                });

            }


        }
    }

    /**
     * 查询设备关联的所有报警规则
     * @param deviceData
     * @return
     */
    @Nullable
    private Set<AlertRule> getAlertRules(DeviceData deviceData) {
        //4.1.1 查询上报数据对应的设备同类型产品下全部设备关联的报警规则列表
        List<AlertRule> alertRuleListForProduct = this.lambdaQuery().eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, -1)
                .eq(AlertRule::getStatus, 1)
                .list();

        //4.1.2 查询报数据对应的设备自己关联的报警规则列表
        List<AlertRule> alertRuleListForDevice = this.lambdaQuery().eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getStatus, 1)
                .list();

        //4.1.3 合并规则列表且去重
        Set<AlertRule> alertRuleListAll = CollUtil.unionDistinct(alertRuleListForProduct, alertRuleListForDevice);
        if (CollUtil.isEmpty(alertRuleListAll)) {
            log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 没有查询到设备关联的报警规则", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId());
            return null;
        }
        return alertRuleListAll;
    }

    /**
     * 设备数据类型转换
     * @param values
     * @return
     */
    @NotNull
    private static List<DeviceData> getDeviceData(List<Object> values) {
        List<DeviceData> deviceDataListAll = new ArrayList<>();
        for (Object value : values) {
            String deviceDataListJSON = (String) value;
            List<DeviceData> deviceDataList = JSONUtil.toList(deviceDataListJSON, DeviceData.class);
            deviceDataListAll.addAll(deviceDataList);
        }
        return deviceDataListAll;
    }

}
